using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
using Xunit;
using Xunit.Abstractions;

namespace Zeus.RemoteProcedureCall.xUnit
{
    public class xUnitTestCore
    {

        ITestOutputHelper outputHelper;

        public xUnitTestCore(ITestOutputHelper output)
        {
            this.outputHelper = output;
        }

        [Fact]
        public async Task ReuseInstanceWithResets_Success()
        {
            var mrvts = new ManualResetValueTaskSource<int>();

            for (short i = 0; i < 48; i++)
            {

                mrvts.SetResult(i);
                // var ignored = Task.Delay(1).ContinueWith(_ => mrvts.SetResult(i));
                var result = await new ValueTask<int>(mrvts, mrvts.Version);

                outputHelper.WriteLine($"i={i} ; result={result}");

                Assert.Equal(i, result);

                mrvts.Reset();
            }
        }

        [Fact]
        public void AccessWrongVersion_Fails()
        {
            var mrvts = new ManualResetValueTaskSource<int>();
            mrvts.Reset();

            Assert.Throws<InvalidOperationException>(() => mrvts.GetResult(1));
            Assert.Throws<InvalidOperationException>(() => mrvts.GetStatus(0));
            Assert.Throws<InvalidOperationException>(() => mrvts.OnCompleted(_ => { }, new object(), 0, ValueTaskSourceOnCompletedFlags.None));

            Assert.Throws<InvalidOperationException>(() => mrvts.GetResult(2));
            Assert.Throws<InvalidOperationException>(() => mrvts.GetStatus(2));
            Assert.Throws<InvalidOperationException>(() => mrvts.OnCompleted(_ => { }, new object(), 2, ValueTaskSourceOnCompletedFlags.None));
        }

        [Fact]
        public void SetTwice_Fails()
        {
            var mrvts = new ManualResetValueTaskSource<int>();

            mrvts.SetResult(42);
            Assert.Throws<InvalidOperationException>(() => mrvts.SetResult(42));
            Assert.Throws<InvalidOperationException>(() => mrvts.SetException(new Exception()));

            mrvts.Reset();
            mrvts.SetException(new Exception());
            Assert.Throws<InvalidOperationException>(() => mrvts.SetResult(42));
            Assert.Throws<InvalidOperationException>(() => mrvts.SetException(new Exception()));
        }

        [Fact]
        public void GetResult_BeforeCompleted_Fails()
        {
            var mrvts = new ManualResetValueTaskSource<int>();
            //mrvts.SetResult(0);
            Assert.Throws<InvalidOperationException>(() => mrvts.GetResult(0));
        }

        [Fact]
        public void SetResult_BeforeOnCompleted_ResultAvailableSynchronously()
        {
            var mrvts = new ManualResetValueTaskSource<int>();
            mrvts.Reset();
            mrvts.Reset();
            Assert.Equal(2, mrvts.Version);

            mrvts.SetResult(42);

            Assert.Equal(ValueTaskSourceStatus.Succeeded, mrvts.GetStatus(2));
            Assert.Equal(42, mrvts.GetResult(2));

            var mres = new ManualResetEventSlim();
            mrvts.OnCompleted(s =>
            {
                Thread.Sleep(1000);
                outputHelper.WriteLine($"ManualResetEventSlim set now");
                ((ManualResetEventSlim)s).Set();
            }, mres, 2, ValueTaskSourceOnCompletedFlags.None);

            outputHelper.WriteLine($"waitting ManualResetEventSlim set now");
            mres.Wait();
            outputHelper.WriteLine($"ManualResetEventSlim set successfully");
            Assert.Equal(2, mrvts.Version);
        }

        [Fact]
        public async Task SetResult_AfterOnCompleted_ResultAvailableAsynchronously()
        {
            var mrvts = new ManualResetValueTaskSource<int>();
            mrvts.Reset();
            mrvts.Reset();
            Assert.Equal(2, mrvts.Version);

            Assert.Equal(ValueTaskSourceStatus.Pending, mrvts.GetStatus(2));
            Assert.Throws<InvalidOperationException>(() => mrvts.GetResult(2));

            var onCompletedRan = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            mrvts.OnCompleted(s => ((TaskCompletionSource<bool>)s).SetResult(true), onCompletedRan, 2, ValueTaskSourceOnCompletedFlags.None);

            Assert.False(onCompletedRan.Task.IsCompleted);
            await Task.Delay(1);
            Assert.False(onCompletedRan.Task.IsCompleted);

            mrvts.SetResult(42);
            Assert.Equal(ValueTaskSourceStatus.Succeeded, mrvts.GetStatus(2));
            Assert.Equal(42, mrvts.GetResult(2));

            Assert.True(onCompletedRan.Task.IsCompleted);
            //await onCompletedRan.Task;

            Assert.Equal(2, mrvts.Version);
        }

        [Fact]
        public void SetException_BeforeOnCompleted_ResultAvailableSynchronously()
        {
            var mrvts = new ManualResetValueTaskSource<int>();
            mrvts.Reset();
            mrvts.Reset();
            Assert.Equal(2, mrvts.Version);

            var e = new FormatException();
            mrvts.SetException(e);

            Assert.Equal(ValueTaskSourceStatus.Faulted, mrvts.GetStatus(2));
            Assert.Same(e, Assert.Throws<FormatException>(() => mrvts.GetResult(2)));

            var mres = new ManualResetEventSlim();
            mrvts.OnCompleted(s => ((ManualResetEventSlim)s).Set(), mres, 2, ValueTaskSourceOnCompletedFlags.None);
            mres.Wait();

            Assert.Equal(2, mrvts.Version);
        }

        [Fact]
        public async Task SetException_AfterOnCompleted_ResultAvailableAsynchronously()
        {
            var mrvts = new ManualResetValueTaskSource<int>();
            mrvts.Reset();
            mrvts.Reset();
            Assert.Equal(2, mrvts.Version);

            Assert.Equal(ValueTaskSourceStatus.Pending, mrvts.GetStatus(2));
            Assert.Throws<InvalidOperationException>(() => mrvts.GetResult(2));

            var onCompletedRan = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            mrvts.OnCompleted(s => ((TaskCompletionSource<bool>)s).SetResult(true), onCompletedRan, 2, ValueTaskSourceOnCompletedFlags.None);

            Assert.False(onCompletedRan.Task.IsCompleted);
            await Task.Delay(1);
            Assert.False(onCompletedRan.Task.IsCompleted);

            var e = new FormatException();
            mrvts.SetException(e);

            Assert.Equal(ValueTaskSourceStatus.Faulted, mrvts.GetStatus(2));
            Assert.Same(e, Assert.Throws<FormatException>(() => mrvts.GetResult(2)));

            await onCompletedRan.Task;

            Assert.Equal(2, mrvts.Version);
        }
    }
}
